﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Devonline.Core;
using Devonline.Entity;
using Devonline.Utils;
using IdentityModel;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using File = System.IO.File;

namespace Devonline.Http
{
    public static class AspNetCoreExtensions
    {
        #region mvc & api extensions
        /// <summary>
        /// 用于依赖注入的 json 全局设置
        /// 为避免客户端字段名首字母大小写的问题, 将不再使用 CamelCase
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static IMvcBuilder AddJsonOptions(this IMvcBuilder builder)
        {
            builder.AddJsonOptions(option =>
            {
                option.JsonSerializerOptions.AllowTrailingCommas = true;
                option.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
                option.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
                option.JsonSerializerOptions.IgnoreReadOnlyFields = false;
                option.JsonSerializerOptions.IgnoreReadOnlyProperties = false;
                option.JsonSerializerOptions.IncludeFields = false;
                option.JsonSerializerOptions.MaxDepth = AppSettings.UNIT_FOUR;
                option.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals;
                option.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
                option.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
                option.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
                option.JsonSerializerOptions.WriteIndented = true;

                // 枚举使用字符串表示形式
                option.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
                //// 时间格式使用自定义
                //option.JsonSerializerOptions.Converters.Add(new DateTimeConverter(AppSettings.DEFAULT_DATETIME_FORMAT));
                //option.JsonSerializerOptions.Converters.Add(new DateTimeOffsetConverter(AppSettings.DEFAULT_DATETIME_FORMAT));
            });

            return builder.AddNewtonsoftJson(option =>
            {
                //使用驼峰样式的key
                option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                //忽略循环引用
                option.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                option.SerializerSettings.TypeNameHandling = TypeNameHandling.None;
                option.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
                //设置时间格式
                option.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
                option.SerializerSettings.DateParseHandling = DateParseHandling.DateTime;
                option.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
                //option.SerializerSettings.DateFormatString = AppSettings.DEFAULT_DATETIME_FORMAT;

                //枚举类型转字符串输出
                option.SerializerSettings.Converters.Add(new StringEnumConverter());
            });
        }
        #endregion

        #region http request extensions
        /// <summary>
        /// 获取查询选项, 将来自 HttpPost 的 OData 查询选项加入到 QueryString 键值对中
        /// </summary>
        /// <param name="request">WebApi 的 HttpRequest 对象</param>
        /// <returns>返回合并后的查询选项</returns>
        public static IEnumerable<KeyValuePair<string, StringValues>> GetRequestOptions(this HttpRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            var queryString = request.Query.ToList();
            if (request.HasFormContentType && (request?.Form?.Keys?.Count ?? 0) > 0)
            {
                queryString.AddRange(request.Form);
            }

            if (request.Headers.IsNotNullOrEmpty())
            {
                queryString.AddRange(request.Headers);
            }

            if (request.Cookies.IsNotNullOrEmpty())
            {
                queryString.AddRange(request.Cookies.ToDictionary(x => x.Key, x => new StringValues(x.Value)));
            }

            return queryString;
        }
        /// <summary>
        /// 从当前 request 中获取参数的值, 取值顺序 Query -> Form -> Header -> Cookie
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="request"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T GetRequestOption<T>(this HttpRequest request, string key) where T : IConvertible
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            var value = string.Empty;
            if (request.Query.Keys.Contains(key))
            {
                value = request.Query[key];
            }
            else if (request.HasFormContentType && request.Form.Keys.IsNotNullOrEmpty() && request.Form.Keys.Contains(key))
            {
                value = request.Form[key];
            }
            else if (request.Headers.IsNotNullOrEmpty() && request.Headers.ContainsKey(key))
            {
                value = request.Headers[key];
            }
            else if (request.Cookies.IsNotNullOrEmpty() && request.Cookies.Keys.Contains(key))
            {
                value = request.Cookies[key];
            }

            if (value.IsNotNullOrEmpty())
            {
                return (T)Convert.ChangeType(value, typeof(T), CultureInfo.CurrentCulture);
            }

            return default;
        }
        /// <summary>
        /// 获取请求的完整 uri 字符串
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public static Uri GetAbsoluteUri(this HttpRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            return new Uri(new StringBuilder()
             .Append(request.Scheme)
             .Append("://")
             .Append(request.Host)
             .Append(request.PathBase)
             .Append(request.Path)
             .Append(request.QueryString)
             .ToString());
        }
        /// <summary>
        /// 通过 request query 获取 QueryOptions 的实例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="request"></param>
        /// <returns></returns>
        public static QueryOptions<T> GetQueryOptions<T>(this HttpRequest request) => new(new QueryOptionRawValue(request.Query));
        /// <summary>
        /// 在不记录日志的情况下, 直接从请求和上下文返回分页的查询结果
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="request"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public static async Task<PagedResult<T>> GetPagedResultAsync<T>(this HttpRequest request, DbContext context) where T : class => await context.GetPagedResultAsync(request.GetQueryOptions<T>());
        #endregion

        #region http context extensions
        /// <summary>
        /// 获取上下文对象中参数/变量的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="httpContext"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T GetContextOption<T>(this HttpContext httpContext, string key) where T : IConvertible
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException(nameof(httpContext));
            }

            var value = httpContext?.User?.Claims?.FirstOrDefault(c => c.Type == key)?.Value;
            if (value.IsNotNullOrWhiteSpace())
            {
                return value.To<T>();
            }
            else
            {
                return httpContext.Request.GetRequestOption<T>(key);
            }
        }
        /// <summary>
        /// 从 httpContext 获取用户标识, 用户标识在 User.Claims 中的 type 为 sub; 在 request 中为 userId
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        public static string GetUserId(this HttpContext httpContext)
        {
            var userId = httpContext?.User?.Claims?.FirstOrDefault(c => c.Type == JwtClaimTypes.Subject)?.Value;
            return userId ?? httpContext.Request.GetRequestOption<string>(nameof(userId));
        }
        #endregion

        #region 文件操作类 api
        /// <summary>
        /// 通用 excel 导入方法, 从 HttpRequest 获取文件名和列名的参数
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="request"></param>
        /// <param name="queryables"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static ExcelData<TEntitySet> Import<TEntitySet, TKey>(this HttpRequest request, Dictionary<string, Func<TEntitySet, object, object>> queryables = null, Action<TEntitySet, IEnumerable<TEntitySet>> action = null) where TEntitySet : class, IEntitySet<TKey>, new()
        {
            string fileName = request.GetRequestOption<string>(nameof(fileName));
            string columnNames = request.GetRequestOption<string>(nameof(columnNames));
            return fileName.Import<TEntitySet, TKey>(columnNames, queryables, action);
        }
        /// <summary>
        /// 通用 excel 导入方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="controller"></param>
        /// <param name="fileName"></param>
        /// <param name="queryables"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static ExcelData<TEntitySet> Import<TEntitySet, TKey>(this string fileName, string columnNames = null, Dictionary<string, Func<TEntitySet, object, object>> queryables = null, Action<TEntitySet, IEnumerable<TEntitySet>> action = null) where TEntitySet : class, IEntitySet<TKey>, new()
        {
            if (fileName.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(fileName));
            }

            using var excelPackage = ExcelUtility.GetExcelPackage(fileName);
            var excelData = new ExcelData<TEntitySet> { FileName = fileName, SheetIndex = 0 };
            if (columnNames.IsNotNullOrEmpty())
            {
                var columns = columnNames.Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries);
                excelData.Columns = columns;
            }

            excelPackage.Import(excelData, queryables, action);
            return excelData;
        }
        /// <summary>
        /// 批量文件上传
        /// </summary>
        /// <typeparam name="TAttachment"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="controller"></param>
        /// <param name="files"></param>
        /// <returns></returns>
        public static async Task<IEnumerable<UploadResult<TKey>>> UploadFilesAsync<TAttachment, TKey>(this IEnumerable<IFormFile> files) where TAttachment : class, IAttachment<TKey>, new()
        {
            if (!files.IsNotNullOrEmpty() || files.Sum(x => x.Length) <= 0)
            {
                throw new Exception("没有提交任何文件!");
            }

            var attachmentSetting = AppSettings.AppSetting?.Attachment;
            var fileSize = attachmentSetting?.TotalUploadFileSize ?? AppSettings.DEFAULT_TOTAL_UPLOAD_FILE_SIZE;
            if (fileSize <= 0)
            {
                fileSize = AppSettings.DEFAULT_TOTAL_UPLOAD_FILE_SIZE;
            }

            if (files.Sum(x => x.Length) > fileSize)
            {
                throw new Exception($"文件总大小超出 {fileSize} 最大上传限制!");
            }

            var filePath = DateTime.UtcNow.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
            var attachmentPath = filePath.GetAttachmentPath();
            if (!Directory.Exists(attachmentPath))
            {
                Directory.CreateDirectory(attachmentPath);
            }

            fileSize = attachmentSetting?.MaxUploadFileSize ?? AppSettings.DEFAULT_MAX_UPLOAD_FILE_SIZE;
            if (fileSize <= 0)
            {
                fileSize = AppSettings.DEFAULT_MAX_UPLOAD_FILE_SIZE;
            }

            var uploadResults = new List<UploadResult<TKey>>();
            var allowFileExtensions = (attachmentSetting?.AllowFileExtensions ?? AppSettings.DEFAULT_ALLOW_FILE_EXTENSIONS).Split(AppSettings.CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries);
            foreach (var file in files)
            {
                var uploadResult = new UploadResult<TKey>
                {
                    FileName = file.FileName,
                    StatusCode = HttpStatusCode.NotAcceptable
                };

                if (file.Length <= 0)
                {
                    uploadResult.Result = "文件内容为空!";
                    uploadResults.Add(uploadResult);
                    continue;
                }

                var ext = Path.GetExtension(file.FileName);
                var extUpper = ext.ToUpperInvariant();
                if (!allowFileExtensions.Any(x => x == extUpper))
                {
                    uploadResult.Result = "文件类型禁止上传!";
                    uploadResults.Add(uploadResult);
                    continue;
                }

                var attachment = new TAttachment()
                {
                    Name = file.FileName,
                    Length = file.Length,
                    Extension = ext,
                    ContentType = file.ContentType
                };

                if (file.Length > fileSize)
                {
                    uploadResult.Result = $"文件 {file.Name} 大小超出 {fileSize} 单文件最大上传限制!";
                    uploadResults.Add(uploadResult);
                    continue;
                }

                var fileName = KeyGenerator.GetKey() + ext;
                attachment.Path = Path.Combine(filePath, fileName);
                fileName = Path.Combine(attachmentPath, fileName);

                try
                {
                    using var stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
                    await file.CopyToAsync(stream).ConfigureAwait(false);
                    uploadResult.StatusCode = HttpStatusCode.OK;
                    uploadResult.Result = attachment.Path;
                    uploadResult.Attachment = attachment;

                    #region 图片文件创建缩略图
                    if (attachment.ContentType.ToUpperInvariant().StartsWith(nameof(Image).ToUpperInvariant(), StringComparison.InvariantCultureIgnoreCase))
                    {
                        var imageThumbnailPrefix = attachmentSetting?.ImageThumbnailPrefix ?? AppSettings.DEFAULT_IMAGE_THUMBNAIL_PREFIX;
                        var imageThumbnailSize = attachmentSetting?.ImageThumbnailSize ?? AppSettings.DEFAULT_IMAGE_THUMBNAIL_SIZE;
                        var newFileName = Path.Combine(attachmentPath, imageThumbnailPrefix + Path.GetFileName(fileName));
                        using var image = Image.FromStream(stream);
                        ImageUtility.CreateThumbnail(image, imageThumbnailSize, imageThumbnailSize, newFileName);
                    }
                    #endregion
                }
                catch (Exception ex)
                {
                    uploadResult.StatusCode = HttpStatusCode.NotAcceptable;
                    uploadResult.Result = ex.Message;
                    Logger.Exception($"文件 {file.Name} 上传失败!", ex);
                }

                uploadResults.Add(uploadResult);
            }

            return uploadResults;
        }
        /// <summary>
        /// 删除附件物理文件, 请在附件相关操作的事物提交之后执行此方法
        /// </summary>
        /// <param name="files"></param>
        public static void Delete(this IEnumerable<string> files, ILogger logger = null)
        {
            if (files.IsNotNullOrEmpty())
            {
                foreach (var file in files)
                {
                    try
                    {
                        logger?.LogInformation("user will delete the file {file}", file);
                        var filePath = file.GetAttachmentPath();
                        if (File.Exists(filePath))
                        {
                            logger?.LogInformation("the file {file} exist, then will delete", file);
                            File.Delete(filePath);
                        }

                        //如果存在文件缩略图则删除
                        var imageThumbnailPrefix = AppSettings.AppSetting?.Attachment?.ImageThumbnailPrefix ?? AppSettings.DEFAULT_IMAGE_THUMBNAIL_PREFIX;
                        var newFileName = Path.Combine(Path.GetDirectoryName(filePath), imageThumbnailPrefix + Path.GetFileName(filePath));
                        if (File.Exists(newFileName))
                        {
                            logger?.LogInformation("the file {file} thumbnail exist, then will delete file thumbnail", file);
                            File.Delete(newFileName);
                        }
                    }
                    catch (Exception ex)
                    {
                        logger?.LogError(ex, "file {file} delete throw exception", file);
                    }
                }
            }
        }
        #endregion
    }
}
